/**
 * @file exercise_sport_test.c
 * note: only for ui demostration
 */

/*********************
*      INCLUDES
*********************/
#include "exercise_sport_test.h"

#if USE_LV_WATCH_EXERCISE != 0

#if EXERCISE_TEST_MODE != 0

#include <stdlib.h>

/*********************
*      DEFINES
*********************/
#define EXERCISE_TEST_MAX_NODE_NUM  (500)

#define EXERCISE_MAX_RECOVERY_TIME  (100) // in hours

/**********************
*      TYPEDEFS
**********************/
typedef struct {
    EXERCISE_TEST_TYPE_T type;
    uint8_t is_started;                               // sport is started if 1
    uint8_t stride;                                   // in meters
    uint8_t distance_trend;
    int8_t height_trend;
    uint16_t total_distance;                          // in meters
    uint16_t total_climb_height;                      // in dm
    uint16_t total_decline_height;                    // in dm
    uint16_t initial_altitude;                        // in dm
    uint16_t highest_altitude;                        // in dm
    uint16_t lowest_altitude;                         // in dm
    uint32_t total_height;                            // in dm
    uint16_t meas_interval;                           // in milliseconds
    uint16_t count;
    uint8_t * distance;                               // in meters
    uint16_t * height;                                // in dm
    uint16_t total_calorie;
    uint16_t hr_zone_time[EXERCISE_TEST_HR_ZONE_NUM]; // in seconds
    uint8_t * hr;
    uint8_t max_hr;
    uint8_t min_hr;
    uint32_t total_hr;
} exercise_sport_record_t;

/**********************
*  STATIC PROTOTYPES
**********************/

/**********************
*  STATIC VARIABLES
**********************/
static exercise_sport_record_t * record = NULL;
static lv_task_t * prepare_task = NULL;
static void * sport_task = NULL;
static uint8_t time_speed = 10; // 1 / time_speed

/**********************
*      MACROS
**********************/

/**********************
 *   STATIC FUNCTIONS
 **********************/
static void exercise_test_get_vo2max_zone(uint8_t * vo2max_zone)
{
    if(NULL == vo2max_zone) {
        printf("%s: warning, vo2max_zone is NULL\n", __FUNCTION__);
        return;
    }

    vo2max_zone[0] = 0;
    vo2max_zone[1] = 26;
    vo2max_zone[2] = 31;
    vo2max_zone[3] = 36;
    vo2max_zone[4] = 41;
    vo2max_zone[5] = 45;
    vo2max_zone[6] = 49;
    vo2max_zone[7] = 61; // used for ui display
}

static void exercise_test_sport_prepare_task_cb(lv_task_t * task)
{
    EXERCISE_TEST_SPORT_PREPARE_CNF cb = (EXERCISE_TEST_SPORT_PREPARE_CNF)task->user_data;
    if(cb) cb();
    prepare_task = NULL;
}

static uint8_t exercise_test_get_max_hr(void)
{
    uint8_t age = 30;
    uint8_t max_hr = 220 - age;
    return(max_hr);
}

static void exercise_test_sport_com_cb(void)
{
    if(NULL == record) {
        printf("%s: warning, record is NULL\n", __FUNCTION__);
        return;
    }

    if((0 < record->count) &&
       (0 == (record->count % EXERCISE_TEST_MAX_NODE_NUM))) {
        uint32_t size = (record->count / EXERCISE_TEST_MAX_NODE_NUM + 1) *
            EXERCISE_TEST_MAX_NODE_NUM;
        if(record->distance) {
            record->distance = lv_mem_realloc(record->distance, size * sizeof(uint8_t));
        }
        if(record->height) {
            record->height = lv_mem_realloc(record->height, size * sizeof(uint16_t));
        }
        if(record->hr) {
            record->hr = lv_mem_realloc(record->hr, size * sizeof(uint8_t));
        }
    }

    srand(Hal_Timer_Get_Counter());
    uint32_t rand_num = rand();

    if(record->hr) {
        uint8_t max_hr = exercise_test_get_max_hr();
        uint8_t last_hr;
        if(0 == record->count) {
            last_hr = record->hr[0];
        } else {
            last_hr = record->hr[record->count - 1];
        }
        uint8_t digit = rand_num % 10;
        record->hr[record->count] = last_hr;
        if(0 == last_hr) {
            record->hr[record->count] = 60;
        } else if(0 == record->is_started) {
            if(6 < digit) {
                record->hr[record->count]++;
            } else if(2 > digit) {
                record->hr[record->count]--;
            }
            if((max_hr * 50 / 100) <= record->hr[record->count]) {
                record->hr[record->count] = max_hr * 50 / 100 - 1;
            }
        } else if((max_hr * 90 / 100) >= last_hr) {
            if((max_hr * 70 / 100) >= last_hr) {
                if(4 < digit) {
                    record->hr[record->count]++;
                } else if(1 > digit) {
                    record->hr[record->count]--;
                }
            } else {
                if(5 < digit) {
                    record->hr[record->count]++;
                } else if(2 > digit) {
                    record->hr[record->count]--;
                }
            }
        } else {
            if(8 < digit) {
                record->hr[record->count]++;
            } else if(6 > digit) {
                record->hr[record->count]--;
            }
        }
        if(50 > record->hr[record->count]) {
            record->hr[record->count] = 50;
        } else if(max_hr < record->hr[record->count]) {
            record->hr[record->count] = max_hr;
        }

        if(record->max_hr < record->hr[record->count]) {
            record->max_hr = record->hr[record->count];
        }
        if(record->min_hr > record->hr[record->count]) {
            record->min_hr = record->hr[record->count];
        }
        record->total_hr += record->hr[record->count];
        record->total_calorie += record->hr[record->count] * 100 / (max_hr * 70);
    }

    if(0 == record->is_started) {
        record->count++;
        return;
    }

    if(record->distance) {
        if(0 == record->count) {
            record->distance[record->count] = rand_num % 2;
            record->distance_trend = (rand_num % 30) + 1; // 1 to 30
        } else {
            record->distance[record->count] = record->distance[record->count - 1];
            uint8_t max_distance = 1;
            uint8_t step_add = 1;
            uint8_t step_sub = 1;
            if(EXERCISE_TEST_TYPE_OUTDOOR_CYCLING == record->type) {
                max_distance = 10;
                step_add = 2;
                step_sub = 3;
            } else if(EXERCISE_TEST_TYPE_OUTDOOR_RUNNING == record->type) {
                max_distance = 4;
                step_add = 3;
                step_sub = 4;
            } else if(EXERCISE_TEST_TYPE_WALKING == record->type) {
                max_distance = 2;
                step_add = 3;
                step_sub = 4;
            }
            if(20 < record->distance_trend) {
                if((0 == (record->distance_trend - 20) % step_add)
                    && (max_distance > record->distance[record->count])) {
                    record->distance[record->count]++;
                }
            } else if(10 < record->distance_trend) {
                if((0 == (record->distance_trend - 20) % step_sub)
                   && (0 < record->distance[record->count])) {
                    record->distance[record->count]--;
                }
            }
            record->distance_trend--;
            if(0 == record->distance_trend % 10) {
                record->distance_trend = (rand_num % 30) + 1; // 1 to 30
                if(max_distance == record->distance[record->count]) {
                    if(20 < record->distance_trend) {
                        record->distance_trend -= 10;
                    }
                } else if(0 == record->distance[record->count]) {
                    if((20 >= record->distance_trend) && (10 < record->distance_trend)) {
                        record->distance_trend += 10;
                    }
                }
            }
        }
        record->total_distance += record->distance[record->count];
    }
    if(record->height) {
        uint16_t cur_height = record->initial_altitude + record->total_climb_height -
            record->total_decline_height;

        if(0 == record->height_trend) {
            record->height_trend = (rand_num % 41) - 20; // -20 to 20
        }

        uint8_t digit = rand_num % 2;
        if(0 < record->height_trend) {
            record->height_trend -= digit;
            if(0 >= record->height_trend) {
                record->height_trend = (rand_num % 41) - 20; // -20 to 20
            }
            record->total_climb_height += digit;
            cur_height += digit;
        } else if(0 > record->height_trend) {
            if(digit > cur_height) {
                cur_height = 0;
                digit = cur_height;
                record->height_trend = (rand_num % 21); // 0 to 20
            } else {
                cur_height -= digit;
                record->height_trend += digit;
                if(0 <= record->height_trend) {
                    record->height_trend = (rand_num % 41) - 20; // -20 to 20
                }
            }
            record->total_decline_height += digit;
        }
        record->height[record->count] = cur_height;
        if(cur_height > record->highest_altitude) {
            record->highest_altitude = cur_height;
        }
        if(cur_height < record->lowest_altitude) {
            record->lowest_altitude = cur_height;
        }
        record->total_height += cur_height;
    }

    if(record->hr) {
        uint8_t hr_zone[EXERCISE_TEST_HR_ZONE_NUM + 1];
        exercise_test_get_hr_zone(hr_zone);
        uint32_t interval = record->meas_interval * time_speed / 1000;
        if(hr_zone[1] >= record->hr[record->count]) {
            record->hr_zone_time[0] += interval;
        } else if(hr_zone[2] >= record->hr[record->count]) {
            record->hr_zone_time[1] += interval;
        } else if(hr_zone[3] >= record->hr[record->count]) {
            record->hr_zone_time[2] += interval;
        } else if(hr_zone[4] >= record->hr[record->count]) {
            record->hr_zone_time[3] += interval;
        } else {
            record->hr_zone_time[4] += interval;
        }
    }

    record->count++;
}

#ifdef BUILD_IN_PC_SIMULATOR
static void exercise_test_sport_cb(lv_task_t * task)
{
    exercise_test_sport_com_cb();
}
#else
static void exercise_test_sport_cb(uint32_t param)
{
    exercise_test_sport_com_cb();
}
#endif

static void exercise_test_calc_training_value(uint8_t * aerobic, uint8_t * anaerobic)
{
    if(NULL == record) return;

    if(aerobic) {
        if(10 >= record->hr_zone_time[2]) {
            *aerobic = record->hr_zone_time[2] / 1;
        } else if(30 >= record->hr_zone_time[2]) {
            *aerobic = 10 + (record->hr_zone_time[2] - 10) / 2;
        } else if(60 >= record->hr_zone_time[2]) {
            *aerobic = 20 + (record->hr_zone_time[2] - 30) / 3;
        } else if(300 >= record->hr_zone_time[2]) {
            *aerobic = 30 + (record->hr_zone_time[2] - 60) / 24;
        } else {
            *aerobic = 40 + (record->hr_zone_time[2] - 300) / 120;
        }
        if(50 < *aerobic) *aerobic = 50;
    }
    if(anaerobic) {
        if(10 >= record->hr_zone_time[3]) {
            *anaerobic = record->hr_zone_time[3] / 1;
        } else if(30 >= record->hr_zone_time[3]) {
            *anaerobic = 10 + (record->hr_zone_time[3] - 10) / 2;
        } else if(60 >= record->hr_zone_time[3]) {
            *anaerobic = 20 + (record->hr_zone_time[3] - 30) / 3;
        } else if(300 >= record->hr_zone_time[3]) {
            *anaerobic = 30 + (record->hr_zone_time[3] - 60) / 24;
        } else {
            *anaerobic = 40 + (record->hr_zone_time[3] - 300) / 120;
        }
        if(50 < *anaerobic) *anaerobic = 50;
    }
}

static void exercise_test_set_sport_record(exercise_test_record_t * data)
{
    if(NULL == data) {
        printf("%s: data is NULL\n", __FUNCTION__);
        return;
    }

    if(0 == record->is_started) {
        printf("%s: warning, sport is not started\n", __FUNCTION__);
        return;
    }

    if(EXERCISE_TEST_CURVE_NODE_NUM > record->count) {
        printf("%s: warning, sport time is too short\n", __FUNCTION__);
        return;
    }

    Hal_Mem_Set(data, 0, sizeof(exercise_test_record_t));

    data->type = record->type;
    printf("%s: save sport data, type %d\n", __FUNCTION__, record->type);

    uint32_t interval = record->meas_interval * time_speed; // in ms
    uint32_t sport_time = record->count * interval / 1000;  // in seconds

    data->recovery_time = record->total_hr * sport_time * 10 / record->count;
    if(10 * 3600 < data->recovery_time) {
        data->recovery_time = (data->recovery_time - 3600 * 10) / 10 + 3600 * 10;
    }
    data->max_recovery_time = EXERCISE_MAX_RECOVERY_TIME * 3600;
    if(data->max_recovery_time < data->recovery_time) {
        data->recovery_time = data->max_recovery_time;
    }

    exercise_test_calc_training_value(&data->aerobic_training_value,
                                      &data->anaerobic_training_value);

    uint16_t node_interval = record->count / EXERCISE_TEST_CURVE_NODE_NUM;
    uint16_t node_extra = record->count % EXERCISE_TEST_CURVE_NODE_NUM;

    uint32_t i = 0;
    uint32_t j = 0;
    uint16_t temp_node_extra = node_extra;
    while(EXERCISE_TEST_CURVE_NODE_NUM > j) {
        uint32_t max_index = i + node_interval;
        uint32_t sum = 0;
        for(;i < max_index;i++) {
            sum += record->hr[i];
        }
        if(0 < temp_node_extra) {
            sum += record->hr[i];
            data->hr[j] = sum / (node_interval + 1);
            i++;
            temp_node_extra--;
        } else {
            data->hr[j] = sum / node_interval;
        }
        j++;
    }
    data->max_hr = record->max_hr;
    data->min_hr = record->min_hr;
    data->aver_hr = record->total_hr / record->count;

    for(i = 0;i < EXERCISE_TEST_HR_ZONE_NUM;i++) {
        data->hr_zone_time[i] = record->hr_zone_time[i] / 60;
        if(30 < (record->hr_zone_time[i] % 60)) {
            data->hr_zone_time[i]++;
        }
    }

    data->total_disctance = record->total_distance;
    data->sport_time = sport_time;
    data->calorie = record->total_calorie;

    if(EXERCISE_TEST_TYPE_FREE_TRAINING == record->type) return;
    if(NULL == record->distance) return;

    data->aver_speed = record->total_distance * 3600 / sport_time;
    if(0 == record->total_distance) {
        data->aver_pace = 0;
    } else {
        data->aver_pace = sport_time * 1000 / record->total_distance;
    }

    uint16_t distance_array[EXERCISE_TEST_CURVE_NODE_NUM] = {}; // in dm
    i = 0;
    j = 0;
    uint16_t max_distance = 0;    // in dm
    uint16_t min_distance = 0xFFFF; // in dm
    temp_node_extra = node_extra;
    uint32_t num = (5 < node_interval) ? node_interval : 5;
    while(EXERCISE_TEST_CURVE_NODE_NUM > j) {
        uint32_t max_index = i + node_interval;
        if(0 < temp_node_extra) {
            max_index++;
            temp_node_extra--;
        }
        uint32_t start_index = (num < max_index) ? (max_index - num) : 0;
        uint32_t sum = 0;
        for(i = start_index;i < max_index;i++) {
            sum += record->distance[i] * 10;
        }
        distance_array[j] = sum / (max_index - start_index);
        if(distance_array[j] > max_distance) {
            max_distance = distance_array[j];
        }
        if(distance_array[j] < min_distance) {
            min_distance = distance_array[j];
        }
        j++;
    }

    if((EXERCISE_TEST_TYPE_OUTDOOR_RUNNING == record->type)
       || (EXERCISE_TEST_TYPE_WALKING == record->type)) {
        data->pace = Hal_Mem_Alloc(sizeof(exercise_test_record_pace_t));
        data->pace->max_pace = interval * 10 / max_distance;

        data->step = Hal_Mem_Alloc(sizeof(exercise_test_record_step_t));
        data->step->step_number = record->total_distance / record->stride;
        data->step->aver_stride_freq = data->step->step_number * 60 / sport_time;
        data->step->aver_stride = record->stride * 100; // in cm
        data->step->max_stride_freq = max_distance * 1000 * 60 / (record->stride * interval * 10);
        data->step->min_stride_freq = min_distance * 1000 * 60 / (record->stride * interval * 10);
        data->step->aver_stride_freq = data->step->step_number * 60 / sport_time;

        uint32_t min_pace = 0;
        uint32_t max_pace = 0xFFFFFFFF;
        for(i = 0;i < EXERCISE_TEST_CURVE_NODE_NUM;i++) {
            if(0 == distance_array[i]) {
                if(0 == i) {
                    data->pace->pace[i] = 0;
                } else {
                    data->pace->pace[i] = data->pace->pace[i - 1];
                }
            } else {
                data->pace->pace[i] = interval * 10 / distance_array[i];
            }
            if(data->pace->pace[i] > min_pace) {
                min_pace = data->pace->pace[i];
            }
            if(data->pace->pace[i] < max_pace) {
                max_pace = data->pace->pace[i];
            }
            data->step->stride_freq[i]
                = distance_array[i] * 1000 * 60 / (record->stride * interval * 10);
        }
        for(i = 0;i < EXERCISE_TEST_CURVE_NODE_NUM;i++) {
            if(0 != data->pace->pace[i]) {
                break;
            }
            data->pace->pace[i] = max_pace;
        }
        for(;i < EXERCISE_TEST_CURVE_NODE_NUM;i++) {
            data->pace->pace[i] = min_pace - data->pace->pace[i] + max_pace;
        }

        data->vo2max = Hal_Mem_Alloc(sizeof(exercise_test_record_vo2max_t));
        data->vo2max->vo2max = (max_distance * 12 * 60 * 1000 / interval - 505) / 45 / 2;
        exercise_test_get_vo2max_zone(data->vo2max->zone);
    } else {
        data->speed = Hal_Mem_Alloc(sizeof(exercise_test_record_speed_t));
        data->speed->top_speed = max_distance * 1000 * 3600 / (interval * 10);
        for(i = 0;i < EXERCISE_TEST_CURVE_NODE_NUM;i++) {
            data->speed->speed[i] = distance_array[i] * 1000 * 3600 / (interval * 10 * 10);
        }
    }

    data->height = Hal_Mem_Alloc(sizeof(exercise_test_record_height_t));
    data->height->climb_height = record->total_climb_height;
    data->height->decline_height = record->total_decline_height;
    i = 0;
    j = 0;
    temp_node_extra = node_extra;
    while(EXERCISE_TEST_CURVE_NODE_NUM > j) {
        uint32_t max_index = i + node_interval;
        uint32_t sum = 0;
        for(;i < max_index;i++) {
            sum += record->height[i];
        }
        if(0 < temp_node_extra) {
            sum += record->height[i];
            data->height->height[j] = sum / (node_interval + 1);
            i++;
            temp_node_extra--;
        } else {
            data->height->height[j] = sum / node_interval;
        }
        j++;
    }
    data->height->max_altitude = record->highest_altitude;
    data->height->min_altitude = record->lowest_altitude;
    data->height->aver_altitude = record->total_height / record->count;
}

/**********************
 *   GLOBAL FUNCTIONS
 ***********************/
void exercise_test_sport_prepare_req(EXERCISE_TEST_TYPE_T type, EXERCISE_TEST_SPORT_PREPARE_CNF cb)
{
    printf("%s: type %d\n", __FUNCTION__, type);

    // turn on sensors to detect sport data

    if((EXERCISE_TEST_TYPE_FREE_TRAINING != type) && (NULL == prepare_task)) {
        prepare_task = lv_task_create(exercise_test_sport_prepare_task_cb, 3000,
                                      LV_TASK_PRIO_HIGH, (void *)cb);
        lv_task_once(prepare_task);
    }

    record = Hal_Mem_Alloc(sizeof(exercise_sport_record_t));
    Hal_Mem_Set(record, 0, sizeof(exercise_sport_record_t));
    record->type = type;
    record->meas_interval = 1000 / time_speed;
    if(EXERCISE_TEST_TYPE_FREE_TRAINING != type) {
        record->distance = Hal_Mem_Alloc(EXERCISE_TEST_MAX_NODE_NUM * sizeof(uint8_t));
        record->height = Hal_Mem_Alloc(EXERCISE_TEST_MAX_NODE_NUM * sizeof(uint16_t));
    }
    record->hr = Hal_Mem_Alloc(EXERCISE_TEST_MAX_NODE_NUM * sizeof(uint8_t));

    if(NULL == sport_task) {
#ifdef BUILD_IN_PC_SIMULATOR
        sport_task = lv_task_create(exercise_test_sport_cb, record->meas_interval,
                                    LV_TASK_PRIO_HIGH, NULL);
#else
        uos_timer_create(&sport_task);
        uos_timer_start(sport_task, MS_TO_TICKS(record->meas_interval),
                        MS_TO_TICKS(record->meas_interval), exercise_test_sport_cb, 0);
#endif
    }

    if(EXERCISE_TEST_TYPE_FREE_TRAINING == type) {
        if(cb) cb();
    }
}

void exercise_test_sport_start_req(void)
{
    if(NULL == record) {
        printf("%s: warning, record is NULL\n", __FUNCTION__);
        return;
    }

    record->is_started = 1;
    record->count = 0;
    record->stride = 1;
    record->height_trend = 0;
    record->initial_altitude = 200; // in dm
    record->highest_altitude = record->initial_altitude;
    record->lowest_altitude = record->initial_altitude;
    record->max_hr = record->hr[0];
    record->min_hr = record->hr[0];
    record->total_hr = record->hr[0];
}

void exercise_test_sport_stop_req(exercise_test_record_t * data)
{
    printf("%s\n", __FUNCTION__);

    if(sport_task) {
#ifdef BUILD_IN_PC_SIMULATOR
        lv_task_del(sport_task);
#else
        uos_timer_delete(sport_task);
#endif
        sport_task = NULL;
    }

    if(prepare_task) {
        lv_task_del(prepare_task);
        prepare_task = NULL;
    }

    if(record) {
        exercise_test_set_sport_record(data);

        if(record->distance) Hal_Mem_Free(record->distance);
        if(record->height) Hal_Mem_Free(record->height);
        if(record->hr) Hal_Mem_Free(record->hr);
        Hal_Mem_Free(record);
        record = NULL;
    }

    // turn off sensors
}

void exercise_test_get_hr_zone(uint8_t * hr_zone)
{
    uint8_t max_hr = exercise_test_get_max_hr();
    hr_zone[0] = max_hr * 50 / 100;
    hr_zone[1] = max_hr * 60 / 100;
    hr_zone[2] = max_hr * 70 / 100;
    hr_zone[3] = max_hr * 80 / 100;
    hr_zone[4] = max_hr * 90 / 100;
    hr_zone[5] = max_hr;
}

void exercise_test_get_sport_data(exercise_test_sport_data_t * data)
{
    if((NULL == record) || (0 == record->count)) {
        printf("%s: warning, not prepare to start sport\n", __FUNCTION__);
        return;
    }

    if(record->hr) {
        data->hr_value = record->hr[record->count - 1];
    }

    if(0 == record->is_started) return;

    uint32_t time = record->meas_interval * time_speed; // in ms

    data->duration = time * record->count / 1000;

    if(record->distance) {
        uint8_t m = 0;
        uint32_t distance = 0;
        for(int16_t k = record->count - 1;(k >= 0) && (m < 5);k--, m++) {
            distance += record->distance[k];
        }
        distance /= m;

        data->pace_value = 0;
        if(0 < distance) {
            data->pace_value = time / distance;
        }

        data->stride_freq_value = distance * 1000 * 60 / (record->stride * time);

        data->speed_value = distance * 1000 * 3600 / time;
    }

    if(0 == record->total_distance) {
        data->aver_pace_value = 0;
    } else {
        data->aver_pace_value = (time * record->count) / record->total_distance;
    }
    
    data->distance_value = record->total_distance;

    data->step_num_value = record->total_distance / record->stride;

    data->climb_value = record->total_climb_height;

    data->height_value = record->initial_altitude + record->total_climb_height -
        record->total_decline_height;

    data->aver_speed_value = record->total_distance * 1000 * 3600 / (time * record->count);

    data->calorie_value = record->total_calorie;

    exercise_test_calc_training_value(&data->aerobic_training_value,
                                      &data->anaerobic_training_value);
}

uint8_t exercise_test_prepare_to_stop_sport(void)
{
    if(NULL == record) {
        printf("%s: warning, record is NULL\n", __FUNCTION__);
        return(0);
    }

    if(0 == record->is_started) {
        printf("%s: warning, sport is not started\n", __FUNCTION__);
        return(0);
    }

    if(EXERCISE_TEST_CURVE_NODE_NUM > record->count) {
        printf("%s: sport time is too short\n", __FUNCTION__);
        return(0);
    }

    return(1);
}

#endif /*EXERCISE_TEST_MODE*/

#endif /*USE_LV_WATCH_EXERCISE*/
